package lastRites.entities;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Vector;

import lastRites.enums.AgentState;
import lastRites.enums.AnimationName;
import lastRites.enums.CreatureType;
import lastRites.enums.Direction;
import lastRites.game.LastRites;
import lastRites.game.LastRitesSettings;
import lastRites.game.interfaces.Rendering;
import lastRites.game.interfaces.Updating;
import lastRites.game.networking.NetworkPackets.PlayerMovementRequest;
import lastRites.game.networking.NetworkPackets.PlayerMovementUpdate;
import lastRites.game.terrain.World;
import lastRites.ui.Inventory;
import lastRites.ui.Item;
import lastRites.ui.ItemStack;
import lastRites.ui.RecipeManager;

import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;

public class Player implements Updating, Rendering {
	private transient float					harvestSpeed			= 5f;
	private float							movementSpeed;
	private transient float					proposedPlayerX;
	private transient float					proposedPlayerY;
	private transient int					collisionFrameX			= 0;
	private transient int					collisionFrameY			= 0;
	private transient int					collisionFrameWidth		= 32;
	private transient int					collisionFrameHeight	= 48;
	private transient boolean				inventoryOpen			= false;
	private transient boolean				pendingValidation;
	private String							name;
	private transient Vector2f				position				= new Vector2f();
	private transient Vector2f				velocity				= new Vector2f();
	private transient Vector2f				facing					= new Vector2f( 0, -1 );
	private Inventory						inventory				= new Inventory();
	private transient Rectangle				drawBox					= new Rectangle( 0, 0, 0, 0 );
	private transient Rectangle				collisionBox			= new Rectangle( 0, 0, 0, 0 );
	private transient AnimationName			currentAnimationName	= AnimationName.MOVE_EAST;
	private transient World					currentWorld;
	private Vector<String>					knownRecipes			= new Vector<String>();
	private transient LinkedList<Vector2f>	movementList			= new LinkedList<Vector2f>();
	private CreatureType					creatureType;
	private transient AgentState			state					= AgentState.IDLING;

	public Player() {
		this( new Vector2f( 0, 0 ) );
	}

	public Player(String name) {
		this();
		this.setName( name );
	}

	public Player(Vector2f pos) {
		this( pos, 150f );
	}

	public Player(Vector2f pos, float speed) {
		this( "Player" + Math.round( Math.random() * 9999 ), pos, speed );
	}

	public Player(String name, Vector2f pos, float speed) {
		this.name = name;
		position = pos.copy();
		movementSpeed = speed;
		inventory.setOwner( this );
		currentAnimationName = AnimationName.IDLE_EAST;
	}

	@Override
	public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException {
		Input inputs = container.getInput();
		// check inputs
		readActionInputs( inputs, delta );
		readMenuInputs( inputs, delta );
		// apply changes
		// maintain effects
		// check collisions etc?
		if( state == AgentState.ATTACKING ) {
			checkHit( delta );
		}
	}

	@Override
	public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
		// Draw me!
		g.drawAnimation( getCurrentAnimation(), getPosition().x, getPosition().y );
		if( LastRitesSettings.DEBUG_DRAW ) {
			g.setColor( Color.white );
			g.draw( getCollisionBox() );
		}
	}

	public void readMenuInputs(Input inputs, int delta) {
		if( !inventoryOpen && inputs.isKeyPressed( Keyboard.KEY_E ) ) {
			openInventory();
		} else if( inventoryOpen && inputs.isKeyPressed( Keyboard.KEY_E ) ) {
			closeInventory();
		}
	}

	public Inventory getInventory() {
		return inventory;
	}

	public void setInventory(Inventory inventory) {
		this.inventory = inventory;
	}

	private void openInventory() {
		inventoryOpen = true;
	}

	private void closeInventory() {
		inventoryOpen = false;
		LastRites.MOUSE_ITEM = null;
	}

	public void readActionInputs(Input inputs, int delta) {
		Vector2f prevFacing = facing.copy();
		float potentialMovement = 0;
		facing.x = 0;
		facing.y = 0;
		// Ability Keys
		if( inputs.isKeyDown( Keyboard.KEY_1 ) ) {
			craft( "WOODEN_WALL_01" );
		}
		if( inputs.isKeyDown( Keyboard.KEY_2 ) ) {
			inventory.addItem( new ItemStack( Item.WOODEN_WALL_01, 1 ) );
		}

		if( ( !state.isBusy() || state == AgentState.ATTACKING ) && inputs.isKeyDown( Keyboard.KEY_SPACE ) ) {
			state = AgentState.ATTACKING;
		} else {
			state = AgentState.IDLING;
		}
		// Movement
		if( inputs.isKeyDown( Keyboard.KEY_W ) ) {
			if( !state.isUnturning() ) {
				facing.y -= 1;
			}
			if( !state.isStationary() && ( potentialMovement = currentWorld.canMove( Direction.NORTH, collisionBox, movementSpeed * delta / 1000 ) ) > 0 ) {
				setProposedPlayerY( position.y - potentialMovement );
				updateMovementInfo();
				state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_A ) ) {
			if( !state.isUnturning() ) {
				facing.x -= 1;
			}
			if( !state.isStationary() && ( potentialMovement = currentWorld.canMove( Direction.WEST, collisionBox, movementSpeed * delta / 1000 ) ) > 0 ) {
				setProposedPlayerX( position.x - potentialMovement );
				updateMovementInfo();
				state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_S ) ) {
			if( !state.isUnturning() ) {
				facing.y += 1;
			}
			if( !state.isStationary() && ( potentialMovement = currentWorld.canMove( Direction.SOUTH, collisionBox, movementSpeed * delta / 1000 ) ) > 0 ) {
				setProposedPlayerY( position.y + potentialMovement );
				updateMovementInfo();
				state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_D ) ) {
			if( !state.isUnturning() ) {
				facing.x += 1;
			}
			if( !state.isStationary() && ( potentialMovement = currentWorld.canMove( Direction.EAST, collisionBox, movementSpeed * delta / 1000 ) ) > 0 ) {
				setProposedPlayerX( position.x + potentialMovement );
				updateMovementInfo();
				state = AgentState.MOVING;
			}
		}
		// Animation determination
		switch( state ) {
			case ATTACKING:
				if( facing.x == 0 && facing.y == 0 ) {
					setCurrentAnimation( AnimationName.valueOf( "ATTACK_" + getCurrentAnimationName().getCardinalDirection().name() ) );
				} else if( facing.x == 0 && facing.y == 1 ) {
					// setCurrentAnimation( AnimationName.ATTACK_SOUTH );
				} else if( facing.x == 0 && facing.y == -1 ) {
					// setCurrentAnimation( AnimationName.ATTACK_NORTH );
				} else if( facing.x == 1 && facing.y == 0 ) {
					setCurrentAnimation( AnimationName.ATTACK_EAST );
				} else if( facing.x == -1 && facing.y == 0 ) {
					setCurrentAnimation( AnimationName.ATTACK_WEST );
				} else if( facing.x != prevFacing.x && facing.y != prevFacing.y ) {
					setCurrentAnimation( facing.x == 1 ? AnimationName.ATTACK_EAST : AnimationName.ATTACK_WEST );
				} else {
					setCurrentAnimation( AnimationName.valueOf( "ATTACK_" + getCurrentAnimationName().getCardinalDirection().name() ) );
				}
				break;
			case CRAFTING:
				break;
			case IDLING:
				setCurrentAnimation( AnimationName.valueOf( "IDLE_" + getCurrentAnimationName().getCardinalDirection().name() ) );
				break;
			case MOVING:
				if( facing.x == 0 && facing.y == 1 ) {
					// setCurrentAnimation( AnimationName.MOVE_SOUTH );
				} else if( facing.x == 0 && facing.y == -1 ) {
					// setCurrentAnimation( AnimationName.MOVE_NORTH );
				} else if( facing.x == 1 && facing.y == 0 ) {
					setCurrentAnimation( AnimationName.MOVE_EAST );
				} else if( facing.x == -1 && facing.y == 0 ) {
					setCurrentAnimation( AnimationName.MOVE_WEST );
				} else if( facing.x != prevFacing.x && facing.y != prevFacing.y ) {
					setCurrentAnimation( facing.x == 1 ? AnimationName.MOVE_EAST : AnimationName.MOVE_WEST );
				} else {
					setCurrentAnimation( AnimationName.valueOf( "MOVE_" + getCurrentAnimationName().getCardinalDirection().name() ) );
				}
				break;
			default:
		}
		applyFriction( delta );
		applyGravity( delta );
		// Update drawboxes
		getDrawBox().setX( position.x );
		getDrawBox().setY( position.y );
		getCollisionBox().setX( position.x + collisionFrameX );
		getCollisionBox().setY( position.y + collisionFrameY );
	}

	private void updateMovementInfo() {
		// If we move then handle it based on the server scenario we're in
		switch( LastRites.GAME_STATUS ) {
			case INGAME:
				if( this.movementList.size() == 0 ) {
					this.movementList.add( new Vector2f( this.getPosition().x, this.getPosition().y ) );
				}
				this.movementList.add( new Vector2f( this.getProposedPlayerX(), this.getProposedPlayerY() ) );
				LastRites.CLIENT.sendTCP( new PlayerMovementRequest( name, proposedPlayerX, proposedPlayerY ) );
				this.setPosition( this.getProposedPlayerX(), this.getProposedPlayerY() );
				break;
			case HOSTINGGAME:
				PlayerMovementUpdate packet = new PlayerMovementUpdate( name, this.getPosition().x, this.getPosition().y );
				LastRites.SERVER.sendToAllTCP( packet );
				// NO BREAK; Allow to flow into SINGLEPLAYER condition
			case SINGLEPLAYER:
				this.setPosition( this.getProposedPlayerX(), this.getProposedPlayerY() );
				break;
		}
	}

	private void applyFriction(int delta) {
		if( getVelocity().x > 0 ) {
			getVelocity().x = Math.max( getVelocity().x - LastRites.FRICTION.x * ( (float)delta / 500 ), 0 );
		}
		if( getVelocity().x < 0 ) {
			getVelocity().x = Math.min( getVelocity().x + LastRites.FRICTION.x * ( (float)delta / 500 ), 0 );
		}
		if( getVelocity().y > 0 ) {
			getVelocity().y = Math.max( getVelocity().y - LastRites.FRICTION.y * ( (float)delta / 500 ), 0 );
		}
		if( getVelocity().y < 0 ) {
			getVelocity().y = Math.min( getVelocity().y + LastRites.FRICTION.y * ( (float)delta / 500 ), 0 );
		}
	}

	private void applyGravity(int delta) {
		float potentialFall;
		if( ( potentialFall = currentWorld.canMove( Direction.SOUTH, collisionBox, LastRites.GRAVITY.y * delta / 1000 ) ) > 0 ) {
			setProposedPlayerY( position.y + potentialFall );
			updateMovementInfo();
		}
	}

	private void checkHit(int delta) {
		// TODO: Check for enemy collision hit
		// determine target square
		Vector2f target = getWorldCellPosition().add( new Vector2f( getCurrentAnimationName().getCardinalDirection().adjX(), getCurrentAnimationName().getCardinalDirection().adjY() ) );
		// apply dmg based on time attacking it(for farming?) or a specific hitframe (more intuitive for combat)
		if( target.y >= 0 && target.x >= 0 && target.y <= currentWorld.getBlocks().length && target.x <= currentWorld.getBlocks()[0].length ) {
			if( currentWorld.getBlocks()[(int)target.y][(int)target.x].isFarmable() ) {
				currentWorld.getBlocks()[(int)target.y][(int)target.x].harvest( this, ( (float)delta / 1000 ) * harvestSpeed );
			}
		}
	}

	public void craft(String name) {
		if( RecipeManager.getRecipe( name ) != null && getInventory().hasItems( RecipeManager.getRecipe( name ).getIngredients() ) ) {
			for( ItemStack s : RecipeManager.getRecipe( name ).getIngredients() ) {
				getInventory().removeItem( s );
			}
			getInventory().addItem( RecipeManager.getRecipe( name ).getFinalProduct() );
		}
	}

	public Rectangle getDrawBox() {
		return drawBox;
	}

	public Rectangle getCollisionBox() {
		return collisionBox;
	}

	public void setCollisionBox(Rectangle collisionBox) {
		this.collisionBox = collisionBox;
		position.x = collisionBox.getMinX();
		position.y = collisionBox.getMinY();
	}

	public Vector2f getPosition() {
		return position;
	}

	public Vector2f getWorldCellPosition() {
		return new Vector2f( (int)( collisionBox.getCenterX() / LastRites.BLOCK_SIZE ), (int)( collisionBox.getCenterY() / LastRites.BLOCK_SIZE ) );
	}

	public void setPosition(Vector2f playerPosition) {
		position = playerPosition.copy();
		collisionBox.setX( position.x );
		collisionBox.setY( position.y );
	}

	public void setPosition(float x, float y) {
		position.x = x;
		position.y = y;
		collisionBox.setX( position.x );
		collisionBox.setY( position.y );
	}

	public void setPositionY(float y) {
		position.y = y;
		collisionBox.setY( position.y );
	}

	public void setPositionX(float x) {
		position.x = x;
		collisionBox.setX( position.x );
	}

	public float getMovementSpeed() {
		return movementSpeed;
	}

	public void setMovementSpeed(float playerSpeed) {
		movementSpeed = playerSpeed;
	}

	public AnimationName getCurrentAnimationName() {
		for( Map.Entry<AnimationName, Animation> a : getMyAnimations().entrySet() ) {
			if( a.getKey() == currentAnimationName ) { return a.getKey(); }
		}
		return null;
	}

	public Animation getCurrentAnimation() {
		return getMyAnimations().get( currentAnimationName );
	}

	public void setCurrentAnimation(AnimationName name) {
		// Clean up animation we might be switching away from
		if( currentAnimationName != null && currentAnimationName != name ) {
			getCurrentAnimation().restart();
		}
		// Set as new one or default to IDLE_EAST
		currentAnimationName = name;
		drawBox.setWidth( getCurrentAnimation().getWidth() );
		drawBox.setHeight( getCurrentAnimation().getHeight() );
		collisionBox.setWidth( collisionFrameWidth );
		collisionBox.setHeight( collisionFrameHeight );
	}

	public void learnRecipe(String name) {
		if( !knownRecipes.contains( name ) ) {
			knownRecipes.add( name );
		}
	}

	private HashMap<AnimationName, Animation> getMyAnimations() {
		return LastRites.ANIMATION_DIRECTORY.get( creatureType );
	}

	public void unlearnRecipe(String name) {
		knownRecipes.remove( name );
	}

	public void unlearnAllRecipes() {
		knownRecipes.clear();
	}

	public Vector2f getCenter() {
		return new Vector2f( position.x + getCurrentAnimation().getCurrentFrame().getWidth() / 2, position.y + getCurrentAnimation().getCurrentFrame().getHeight() / 2 );
	}

	public boolean isInventoryOpen() {
		return inventoryOpen;
	}

	public void setInventoryOpen(boolean inventoryOpen) {
		this.inventoryOpen = inventoryOpen;
	}

	public Vector2f getFacing() {
		return facing;
	}

	public void setFacing(Vector2f facing) {
		this.facing = facing.copy();
	}

	public int getCollisionFrameX() {
		return collisionFrameX;
	}

	public void setCollisionFrameX(int collisionFrameX) {
		this.collisionFrameX = collisionFrameX;
	}

	public int getCollisionFrameY() {
		return collisionFrameY;
	}

	public void setCollisionFrameY(int collisionFrameY) {
		this.collisionFrameY = collisionFrameY;
	}

	public AgentState getState() {
		return state;
	}

	public void setState(AgentState state) {
		this.state = state;
	}

	public float getHarvestSpeed() {
		return harvestSpeed;
	}

	public void setHarvestSpeed(float harvestSpeed) {
		this.harvestSpeed = harvestSpeed;
	}

	public Vector2f getVelocity() {
		return velocity;
	}

	public void setVelocity(Vector2f velocity) {
		this.velocity = velocity.copy();
	}

	public CreatureType getCreatureType() {
		return creatureType;
	}

	public void setCreatureType(CreatureType creatureType) {
		this.creatureType = creatureType;
	}

	public World getCurrentWorld() {
		return currentWorld;
	}

	public void setCurrentWorld(World currentWorld) {
		this.currentWorld = currentWorld;
	}

	public void setCurrentAnimationName(AnimationName currentAnimationName) {
		this.currentAnimationName = currentAnimationName;
	}

	public LinkedList<Vector2f> getMovementList() {
		return movementList;
	}

	public void setMovementList(LinkedList<Vector2f> movementList) {
		this.movementList = movementList;
	}

	public float getProposedPlayerY() {
		return proposedPlayerY;
	}

	public float getProposedPlayerX() {
		return proposedPlayerX;
	}

	public void setProposedPlayerY(float y) {
		proposedPlayerY = y;
	}

	public void setProposedPlayerX(float x) {
		proposedPlayerX = x;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setPendingValidation(boolean pendingValidation) {
		this.pendingValidation = pendingValidation;
	}

	public boolean isPendingValidation() {
		return pendingValidation;
	}
}
